;   This program is free software: you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;
;程序名称:fbird.asm(未完成版)
;功能:flappyBird游戏
;环境:Windows视窗游戏
;编译器:MASM32 
;用法:看说明
;返回值:没有
;破坏寄存器:不适用
;
;用Win32汇编写FlappyBird 

;FlappyBird未完成版，尚欠碰撞测试，拿金币，计分，生命， 
;GameOver，音乐，音效，关卡设计等等，完成度20%


;材料:-
;
;1.masm32版,网上自找,最好用  \MASM32  作为文件夹
;2.FlappyBird背景图,文字图,柱子及小鸟图,网上抓
;3.Photoshop任何版本,把背景图裁剪为游戏窗口的1/4大小
;若游戏窗口是800x400,背景图做成200x400,(见压缩包里的4back.bmp)
;注意左右拼合起来时,不要看见接驳痕迹.
;把抓回来的小鸟褪去背景色,改为全黑背景(透明色),
;利用旋转另制作七个小鸟的方向图,分别为
;右,右下,下,左下,左,左上,上,右上
;合拼八图,增加版面,把flappybird横牌和上下柱子合成一图
;(见压缩包里的birdAA.bmp)
;记录每一个图像在合拼图的位置和宽长(参看代码中的birdA_offset)

;原理:-
;
;1.这种横向2D游戏通常同时有背景卷动和主角移动,我们设定了一个55ms的计时器
;invoke SetTimer,NULL,NULL,55,addr _ProcTimer 
;即是说, 每55ms(1/18秒)系统会呼叫 _ProcTimer 一次,大部份工作都在这程序完成.
;
;2.读取背景图,4个背景图合成一张游戏窗口背景图,
;invoke LoadBitmap,hInstance,100       其中100是背景图编号,由rsrc.rc指定
;为什么要合拼而不完整一张呢?原因是bmp挺大,一个大bmp随时1m以上,
;若改为jpg需额外代码处理,不如bmp方便
;用BitBlt函数连续做12(4x3)次合拼,做了一张3个窗口大小的背景暂存(memDC2)
;我们叫它1号窗口,2号窗口和3号窗口
;另外建立一个窗口大小的显示暂存(memDC3)
;设定一个卷动变数var1,每1/18秒轮到_ProcTimer运行时,var1+5
;1,2号窗口是一张1600宽的暂存图(memDC2的1和2),每次显示时,
;由x=var1,y=0的位置开始搬(memDC2)800x400的资料出来放显示暂存(memDC3)
;再把小鸟动作,柱子,金币或各种物件依位置放在显示暂存(memDC3)
;最后刷新屏幕时由显示暂存(memDC3)一次置入当前窗口中,
;由于var1不断增加,x不断变动,这样就会做成背景卷动和主角移动的效果
;当var1累加到达800,视界也到达第2号窗口,这时var1置0,
;2号窗口资料搬到1号窗口,3号窗的原始背景移到2号窗口,重新绘上柱子或其他物件
;这样,柱子和新物件就源源不断由右方卷出来
;
;3.原代码中有详细注解,在这里也不再多说了,若有不明白地方欢迎提出,
;更欢迎指出不足之处,毕竟俺也少写这类程式,错漏难免....
;
;4注意:这只是一个范例程式，若你需要其他功能或加入或删减任何代码，
;请随便，但不能要求作者为你订制特定的功能或,根据功课要求而改变这
;个子程序改变那个子程序，若是代码错误或优化代码的意见，
;则欢迎提出:)
;
;压缩包:-
;链接：http://pan.baidu.com/s/1o6IL6gM 密码：ecx1
;压缩包,有源码,资源档,图档和make.bat
;运行make.bat即可完成编译
;
;---------------------- 以下是flappyBird源码 ----------------------------
;
picW equ 200    ;背景图宽
boardW equ picW * 4	;背景图 x 4 = 游戏窗口
boardH equ 400 	;背景图高
myGameX equ 120	;游戏窗口起始坐标x
myGameY equ 100	;游戏窗口起始坐标y
BirdSW	equ  50	;小鸟图宽
BirdSH	equ  38	;小鸟图高
pic_W	equ 130	;资料图宽	  ,资料图包括小鸟,柱子,金币,flappybird
pic_H	equ 295	;资料图高
FlagSizeW equ 79 	;FlappyBird图宽
FlagSizeH equ 24	;FlappyBird图高; 在资料图位置  =   pic_H - FlagSizeH
MovGap	equ 1	;每次动作移动量
down_speed equ 7	;自由落下移动量
FlyLimitX equ boardW -(BirdSW + 8 ) ;右边界=游戏窗口宽-(小鸟宽+调整值)
FlyLimitY equ boardH - (BirdSH + 60) ;下边界=游戏窗口高-(小鸟高+地台高)  
pillarW	equ 	35	;柱子宽
pillarH	equ	293	;柱子高

; #########################################################################
; include档路径,可自行调整修改
include \masm32\include\masm32rt.inc
INCLUDE \MASM32\INCLUDE\advapi32.inc
INCLUDE \MASM32\INCLUDE\winmm.inc
INCLUDE \MASM32\INCLUDE\msimg32.inc
INCLUDELIB \MASM32\LIB\advapi32.LIB
INCLUDELIB \MASM32\LIB\winmm.LIB
INCLUDELIB \MASM32\LIB\msimg32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

; #########################################################################

.data
memDC0        dd 0 	;背景图句柄
memDC2        dd 0	;3个游戏窗口大小的句柄,1,2窗口随游戏变化
memDC3        dd 0	;当前游戏窗口暂存句柄
memDC9        dd 0	;游戏资料图句柄
memBmp        dd 0	;背景图内存
memBmp2       dd 0	;3个游戏窗口大小内存
memBmp3       dd 0	;当前游戏窗口内存
memBmp9       dd 0	;游戏资料图内存

idTimer	dd -1	;计时
BirdX 	dd boardW / 2 - 200	;小鸟初始位置x
BirdY 	dd boardH / 2	;小鸟初始位置y
Start_Random dd 0		;乱数开关
szDisplayName  db "Flappy Bird",0 

birdA_offset label dword	;8个小鸟图在资料图的位置和大小, (x,y)(宽,高)
Bird_right          dd 	0,0,50,38		;→  0,  以下是图编号
Bird_right_down     dd	0,76,50,38		;↘  1
Bird_down           dd	50,0,38,50		;↓ 2
Bird_left_down      dd	0,114,50,42	;↙ 3
Bird_left           dd	0,157,50,38	;←  4 
Bird_left_up        dd	52,100,40,50	;↖ 5
Bird_up             dd	50,50,38,50	;↑ 6
Bird_right_up       dd 	0,38,50,38		;↗ 7
fname_offset        dd 	0,pic_H - FlagSizeH,79,24 ;8 pic_H - FlagSizeH
pillar1_offset      dd  	94,0,pillarW,pillarH 	;9   ;柱子1
pillar2_offset      dd  	129,0,pillarW,pillarH 	;10 ;柱子2
coin_offset         dd 	0,195,30,31	;11 ;金币

;以下是小鸟方向编号
Right_No		equ	0	;右
Right_Down_No   equ	1	;右下
Down_No         equ	2	;下
Left_Down_No    equ	3	;左下
Left_No         equ	4	;左
Left_Up_No      equ	5	;左上
Up_No           equ	6	;上
Right_Up_No     equ	7	;右上
fname_no        equ	8	;flappybird 牌子
pillar_no       equ	9  ; and 10 ;柱子
coin_no         equ	11	;金币
ReAction        equ	0AA01h 	; 重复动作编号
StopAction      equ	0AA02h	; 停止动作编号

;小鸟动作: [哪小鸟图], x (正值向右,负值向左), y (正值向下,负值向上) , [哪小鸟图], x , y ......................
;动作可自由修改,最终须以StopAction作结!
no_action 		    dd	StopAction
fly_Space_action 	dd 	Right_Up_No,0,-7,Right_Up_No,0,-9,Right_Up_No,0,-7,Right_Up_No,0,-7,Right_Up_No,0,-7,Right_Up_No,0,-5,Right_Up_No,0,-5,Right_Up_No,0,-5,StopAction 	;picture No + X , Y
fly_right_action 	dd 	Right_Down_No,15,6,Right_Down_No,15,6,Right_Down_No,15,6,Down_No,10,6,Left_Down_No,-6,0,Left_Up_No,0,-6,Up_No,0,-6,Up_No,0,-3,Right_Up_No,-3,-6,StopAction
fall_right_action 	dd 	Right_Down_No,3,0,Down_No,3,0,Left_Down_No,3,0,Left_No,3,0, Up_No,3,0,Right_Up_No,2,0,Right_Down_No,2,0,StopAction
fall_Left_action 	dd 	Right_Up_No,-3,0,Up_No,-3,0,Left_Up_No,-3,0,Left_Down_No,-3,0,Left_No,-3,0,Left_Up_No,-2,0,Up_No,0,0,StopAction
fall_down_action 	dd 	Right_Down_No,0,9,Down_No,0,9,Left_Down_No,0,9,Left_No,0,9,Up_No,0,9,Right_Up_No,0,9,Right_No,0,9,ReAction	;ReRun 
fly_down_action 	dd 	Down_No,0,20,Down_No,0,20,Down_No,0,20,Down_No,0,20,Down_No,0,15,Down_No,0,5,StopAction	 	;picture + speed
fly_Left_action 	dd	Right_Up_No,5,-15,Right_Up_No,5,-15,Right_Up_No,5,-10,Up_No,5,-15,Up_No,5,-15,Up_No,5,-10,Up_No,0,-10,Left_Up_No,-15,-5,Left_Up_No,-15,0,Left_No,-15,10,Left_No,-10,10,Left_No,-5,10,Left_Down_No,-5,10,Down_No,0,10,Right_Down_No,0,10,StopAction
fly_Up_action       dd	Right_Up_No,0,-20,Up_No,0,-20,Up_No,0,-20,Up_No,0,-20,Right_Up_No,0,-10,Right_No,0,5,StopAction
action_mode         dd	0 ;开始动作,直到读到stopAction,set action_mode=0,静止=1
action_count        dd	0 ;动作计数,做到那一步
stillness_flag      dd	0 ;掉到地上,碰到柱子皆会设定静止模式=1, 直到空白起飞设=0
current_bird_no     dd	0 ;当前小鸟方向

;以下是小鸟动作编号
action_list	label dword	
dd offset no_action		    ;0  无动作
dd offset fly_Space_action	;1 按空白
dd offset fly_right_action	;2 按右,空中打滚,完成后向右稍稍移动
dd offset fall_right_action	;3 掉到地上,向右滚动
dd offset fall_Left_action	;4 掉到地上,向左滚动
dd offset fall_down_action	;5 连续丢下,   (碰撞测试后才启用)
dd offset fly_down_action 	;6 按下,垂直向下飞
dd offset fly_Left_action 	;7 按左, 空中打滚,完成后向左稍稍移动
dd offset fly_Up_action	;8 按上 ,垂直向上飞

pillar_offset label dword	;柱子位置,三个值分别是 : x轴, 上或下(0=上,1=下,2=无),长度
pillar1     dd  60,1,170		;以下是初始位置,之后由乱数修改
pillar_dataL equ $ - offset pillar1
pillar2     dd  200+110,0,210
pillar3     dd  (2*200+100),1,110
pillar4     dd  (3*200+80),0,190
last_pillar dd 0  		;上次柱子值是上或下
same_pillar dd 0		;上下柱子值计数,若三个相同,则相反
seed        dd 0FFAABB11h  	;乱数种子

random_table label dword ;乱数决定柱子位置,以下是柱子限制表(位置限制,0=上 1=下 2=无,长度限制)
dd 0+30,199-(pillarW+30),0,2,90,200 ;pillar position , active or not(0,1,2) ,  pillar length
dd 200+30,399-(pillarW+30),0,2,90,200
dd 400+30,599-(pillarW+30),0,2,90,200
dd 600+30,799-(pillarW+30),0,2,90,200
Start_Random_limit equ ($ - offset random_table)/8    ;产生多少乱数

.data?
hInstance HINSTANCE ? 
CommandLine LPSTR ? 
G_hwnd dd ?	;本视窗句柄,全局
var1 dd ?		;背景移动量变数

.code

; --------------------- 程式开始,以下是win32窗口框架,可以无视 ------------------------

start:			
      invoke GetModuleHandle, NULL
      mov hInstance, eax
      invoke GetCommandLine
      mov CommandLine, eax
      invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
      invoke ExitProcess,eax

; #########################################################################

WinMain proc hInst     :DWORD,
             hPrevInst :DWORD,
             CmdLine   :DWORD,
             CmdShow   :DWORD

      ;====================
      ; Put LOCALs on stack
      ;====================

      LOCAL wc   :WNDCLASSEX
      LOCAL msg  :MSG
      LOCAL hWd  :HWND


      ;==================================================
      ; Fill WNDCLASSEX structure with required variables
      ;==================================================

      szText szClassName,"Project_Class"
      mov wc.cbSize,         sizeof WNDCLASSEX
      mov wc.style,          CS_BYTEALIGNWINDOW
      mov wc.lpfnWndProc,    offset WndProc
      mov wc.cbClsExtra,     NULL
      mov wc.cbWndExtra,     NULL
      push  hInst 
      pop   wc.hInstance 
      mov wc.hbrBackground,  COLOR_BTNFACE+1
      mov wc.lpszMenuName,   NULL
      mov wc.lpszClassName,  offset szClassName
      invoke LoadIcon,hInst,500    ; icon ID
      mov wc.hIcon, eax
     mov wc.hIconSm,0
        invoke LoadCursor,NULL,IDC_ARROW
      mov wc.hCursor,        eax
      invoke RegisterClassEx, ADDR wc

      ;================================
      ; Centre window at following size
      ;================================

      invoke CreateWindowEx,WS_EX_LEFT,
                            ADDR szClassName,
                            ADDR szDisplayName,
                            WS_OVERLAPPEDWINDOW,
                            myGameX,myGameY,boardW,boardH,
                            NULL,NULL,
                            hInst,NULL
      mov   hWd,eax
      mov  G_hwnd,eax		;增加一个全局窗口句柄
      invoke LoadMenu,hInst,600  ; menu ID
      invoke SetMenu,hWd,eax

      invoke ShowWindow,hWd,SW_SHOWNORMAL
      invoke UpdateWindow,hWd

      ;===================================
      ; Loop until PostQuitMessage is sent
      ;===================================

    StartLoop:
      invoke GetMessage,ADDR msg,NULL,0,0
      cmp eax, 0
      je ExitLoop
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage,  ADDR msg
      jmp StartLoop
    ExitLoop:

      return msg.wParam

WinMain endp

; #########################################################################

; ------------------------- 以上是win32窗口框架,可以无视 ---------------------------------


;----------------------------------------------------------------------------------------------
;以下是55ms运行一次的程序,负责刷新画面,把小鸟,柱子,金币等贴到暂存区

_ProcTimer proc    _hWnd,_uMsg,_idEvent,_dwTime
	LOCAL hDC    :DWORD		

	cmp action_mode,0		;是否已有动作模式?
	jnz @f			;有,去@@处理小鸟动作
	cmp stillness_flag,1		;是否静止模式?
	jz ProcTimeX		;是,离开,刷新停止
	mov ecx,down_speed		;自由落下次数 , 7
ProcTime10:
	call Check_Down		;检查落下是否合法
	loop ProcTime10		;再做
	cmp action_mode,0		;是否动作模式,因有落下,可能已触碰地面,要做翻滚动作
	jz ProcTime40		;不是,去做背景卷动

@@:	;action!		;以下处理所有动作模式,包括上下右左飞行,滚动,掉下等等
	mov esi,action_mode	;取哪一动作
	shl esi,2		;x4
	add esi,offset action_list ;指向动作列表
	mov esi,[esi]	;取动作流程地址
	mov eax,action_count  ;取动作计数, 即轮到哪一个动作
	mov ebx,eax	;存
	shl ebx,2		;x 4
	shl eax,3		;x 8
	add eax,ebx	;x 12
	add esi,eax		;每套动作占12bytes,包括小鸟方向,x移动,y移动
	mov eax,[esi]	;取第1值
	cmp eax,ReAction	;是否循环动作	
	jnz @f		;不是
	mov action_count,0	;重设动作计数
	jmp short @b	;回头再读取,即循环运行该套动作

@@:	cmp eax,StopAction	;是否停止动作	
	jnz @f		;不是
	mov action_mode,0	;关闭动作模式
	mov current_bird_no,0  ;重设小鸟正常方向,向右
	mov action_count,0	;重设动作计数
	jmp ProcTimeX	;离开

@@:	mov current_bird_no,eax  ;存下当前小鸟方向
	mov eax,[esi+4]	  ;取x轴移动
	add eax,BirdX	;加原来小鸟x位置
	cmp eax,0		;比较0
	jb @f		
	cmp eax,FlyLimitX     ;与边界限制比较
	ja @f		;大于则不变
	mov BirdX,eax	;存新小鸟位置
@@:	mov ecx,[esi+8]	;取y轴移动
	cmp ecx,0			
	jg ProcTime20	;大于去检查下移
	je ProcTime30	;等于,离开,不做上下移动
	neg ecx		;补数,得上移量
@@:	call Check_UP	;检查上移正确性
	loop @b		;做ecx次
	jmp short ProcTime30  ;离开

ProcTime20:
	call Check_Down	;检查下移正确性
	loop ProcTime20	;做ecx次

ProcTime30:
	inc action_count	;累加动作计数

ProcTime40:
	cmp var1,boardW	;比较卷动量和窗口宽
	jbe ProcTime90	;少于去 ProcTime90

	mov var1,0		;重置卷动量=0
	; part 2 - > part 1	;卷动了一个屏幕,把memDC2里3个窗口的第2个覆制到第1个
	invoke StretchBlt,memDC2,0,0,boardW,boardH,memDC2,boardW,0,boardW,boardH,SRCCOPY
	; part 3 - > part 2	;把memDC2里3个窗口的第3个覆制到第2个,即清除第2个窗口的所有杂物,只留下背景,
			;然后重新在第2个窗口绘制柱子和金币.
	invoke StretchBlt,memDC2,boardW,0,boardW,boardH,memDC2,boardW*2,0,boardW,boardH,SRCCOPY
	mov esi,offset pillar1	;指向柱子资料区
	mov ecx,4		;最多四个柱子
ProcTime50:
	push esi
	push ecx
	call Check_pillar	;检查并取得柱子相关资料
	jc ProcTime80	;没有柱子,下一个
	; 贴上柱子
	invoke TransparentBlt,memDC2,ebx,edi,pillarW,edx,memDC9,ecx,eax,pillarW,edx,0

	mov edx, 30 +25	;edx =  (金币宽 + 偏移量)
	cmp ebx,boardW + 400 ; 柱子位置在屏幕左半还是右半边 ?
	jb ProcTime60	;左面
	mov edx, - (30 +25)   ;edx =   -  (金币宽 + 偏移量)
ProcTime60:	
	add ebx,edx	;若柱子位置在屏幕中间偏左,则金币在柱子右侧,否则相反
	call random 	;取乱数
	xor edx,edx	;清0
	mov ecx,3		;
	div ecx		;除3,则edx = 0,1,2
	cmp edx,1		; 1 ?
	mov edi,7		;金币位置,设定y轴距离顶7像素
	jb ProcTime70	;edx = 0 到 ProcTime70
	ja ProcTime80	;edx = 2,表示没有金币
	mov edi,boardH - ( 60 + 30 + 7)	;edx = 1,金币位置,设定y轴距离地面7像素
ProcTime70:
	mov esi,offset coin_offset ;指向金币资料
	mov eax,[esi]	;取出在资料图的位置x
	mov ecx,[esi+4]	;取出在资料图的位置y
	;以下贴出金币
	invoke TransparentBlt,memDC2,ebx,edi,30,30,memDC9,eax,ecx,30,30,0	

ProcTime80:
	pop ecx
	pop esi
	add esi,pillar_dataL	;指向下一柱子
	dec ecx
	or ecx,ecx
	jz @f
	jmp ProcTime50
@@:
	mov Start_Random,1	;贴完一个窗口的柱子,启动下一次的乱数开关
ProcTime90:
	;以下根据卷动变量var1,取出暂存区memDC2里一画面的资料放入memDC3
	invoke BitBlt,memDC3,0,0,boardW,boardH,memDC2,var1,0,SRCCOPY
	add var1,5	;增加卷动量

	mov esi,offset fname_offset  ;指向flappyBird 牌子	
	mov eax,[esi]	;get x
	mov ebx,[esi+4]	;get y
	mov ecx,[esi+8]	;get w
	mov edx,[esi+12]	;get h
	;以下贴出flappyBird 牌子	
	invoke TransparentBlt,memDC3,0,0 ,FlagSizeW,FlagSizeH,memDC9,eax,ebx,ecx,edx,0	; 标记

	mov eax,0		
	cmp action_mode,0	;是否有动作模式
	jz @f		;没有
	mov eax,current_bird_no ;取当前小鸟方向编号
@@:	shl eax,4		;x 16  
	mov esi,offset birdA_offset ;指向正确小鸟方向资料段,依次取出位置和大小
	add esi,eax
	mov eax,[esi]	;get x
	mov ebx,[esi+4]	;get y
	mov ecx,[esi+8]	;get w
	mov edx,[esi+12]	;get h

	;以下贴出小鸟
	invoke TransparentBlt,memDC3,BirdX,BirdY,ecx,edx,memDC9,eax,ebx,ecx,edx,0 ; diff bird or pic

	;全部贴图完成,存于memDC3.待下一次刷屏由memDC3取出存入hDC即可
	;以下直接呼叫窗口刷新
	invoke InvalidateRect,G_hwnd,NULL,FALSE                ;redraw

ProcTimeX:
	ret

_ProcTimer	endp


;------------------------------- 以下是游戏主程序   -------------------------------------------
WndProc proc hWnd:DWORD,uMsg   :DWORD,wParam :DWORD,lParam :DWORD

    LOCAL var    :DWORD
    LOCAL caW    :DWORD
    LOCAL caH    :DWORD
    LOCAL Rct    :RECT
    LOCAL hDC    :DWORD
    LOCAL Ps     :PAINTSTRUCT
    LOCAL buffer1[128]:BYTE  ; these are two spare buffers
    LOCAL buffer2[128]:BYTE  ; for text manipulation etc..


    .if uMsg == WM_COMMAND

    ;======== menu commands ========

    .elseif uMsg == WM_CREATE	;以下建立窗口时的初始设定程序

	;设定每55ms执行一次的计时程序,用以背景卷动和小鸟移动刷新
	invoke	SetTimer,NULL,NULL,55,addr _ProcTimer 
	mov idTimer,eax  ;计时句柄

	invoke GetDC,hWnd	;获取当前窗口句柄
	mov hDC,eax	;存

	invoke LoadBitmap,hInstance,100    ;载入背景图
	mov memBmp, eax		   ;存句柄
	invoke CreateCompatibleDC,hDC	   ;建立和当前窗口句柄相容的显示句柄
	mov memDC0,eax		    ;存
	invoke SelectObject,memDC0,memBmp ;选取背景图放入

	invoke LoadBitmap,hInstance,102   ; 载入资料图
	mov memBmp9, eax
	invoke CreateCompatibleDC,hDC
	mov memDC9,eax
	invoke SelectObject,memDC9,memBmp9

	invoke CreateCompatibleDC,hDC  ;建立暂存显示句柄 ,大小为背景图的12倍,因为背景图以有1/4个窗口大小
	mov memDC2,eax		;存
	invoke CreateCompatibleBitmap,hDC,picW * 12, boardH  ; 3 x hDC空间,1显示,2缓冲,3永久
	mov memBmp2,eax
	invoke SelectObject,memDC2,memBmp2 

	invoke CreateCompatibleDC,hDC	; 显示句柄,和游戏窗口同样大小,用以暂时存放显示资料
	mov memDC3,eax
	invoke CreateCompatibleBitmap,hDC,picW * 4, boardH
	mov memBmp3,eax
	invoke SelectObject,memDC3,memBmp3

	invoke BitBlt,memDC2,0,0,picW,boardH,memDC0,0,0,SRCCOPY   ;将200,400的背景图放在(200x12),400的1/12位置 
	invoke BitBlt,memDC2,picW,0,picW*2 ,boardH,memDC0,0,0,SRCCOPY  ;2/12位置 
	invoke BitBlt,memDC2,picW*2,0,picW*3 ,boardH,memDC0,0,0,SRCCOPY ;3/12位置 
	invoke BitBlt,memDC2,picW*3,0,picW*4 ,boardH,memDC0,0,0,SRCCOPY ;4/12位置 
	invoke BitBlt,memDC2,picW*4,0,picW*5 ,boardH,memDC0,0,0,SRCCOPY ;5/12位置 
	invoke BitBlt,memDC2,picW*5,0,picW*6 ,boardH,memDC0,0,0,SRCCOPY ;6/12位置 
	invoke BitBlt,memDC2,picW*6,0,picW*7 ,boardH,memDC0,0,0,SRCCOPY ;7/12位置 
	invoke BitBlt,memDC2,picW*7,0,picW*8 ,boardH,memDC0,0,0,SRCCOPY ;8/12位置 
	invoke BitBlt,memDC2,picW*8,0,picW*9 ,boardH,memDC0,0,0,SRCCOPY ;9/12位置  
	invoke BitBlt,memDC2,picW*9,0,picW*10 ,boardH,memDC0,0,0,SRCCOPY ;10/12位置 
	invoke BitBlt,memDC2,picW*10,0,picW*11 ,boardH,memDC0,0,0,SRCCOPY ;11/12位置 
	invoke BitBlt,memDC2,picW*11,0,picW*12 ,boardH,memDC0,0,0,SRCCOPY ;12/12位置 
	;以上完成 3窗口的背景图,1号,2号背景作卷动和柱子,金币等暂存,3号背景作永久背景暂存

	;第一次刷新暂存图,把1号背景存入memDC3, memDC3之后会放小鸟和flappy招牌
	invoke StretchBlt,memDC3,0,0,boardW,boardH,memDC2,0,0,boardW,boardH,SRCCOPY
	;
	;以下放第一次的柱子
	mov esi,offset pillar1	;指向柱子资料区
	mov ecx,4		;一个游戏窗口最多4支柱子		
@@:	
	push esi
	push ecx
	call Check_pillar ;检查并计算柱子摆放位置,柱子图位置,长度等,由暂存器回传,参看Check_pillar子程序
	jc No_pillar    ; 若cf表示没有柱子,跳过	
	;贴出柱子, TransparentBlt函数可略过透明色,此游戏所有透明色=黑色
	invoke TransparentBlt,memDC2,ebx,edi,pillarW,edx,memDC9,ecx,eax,pillarW,edx,0   ; 0=透明色=黑色
No_pillar:
	pop ecx
	pop esi
	add esi,pillar_dataL	;下一柱子
	loop @b

	mov Start_Random,1	;乱数开关开启,除了第一次四支柱子,以后的都是乱数

	invoke ReleaseDC,hWnd,hDC	;以下移除暂时句柄
	invoke DeleteObject,memBmp
	;invoke DeleteObject,memBmp1
	invoke DeleteObject,memBmp2
	invoke DeleteObject,memBmp3
	invoke DeleteDC,memDC0
	invoke DeleteObject,memBmp9

    .elseif uMsg == WM_SIZE

    .elseif uMsg == WM_PAINT	;以下是窗口刷新,每次窗口被遮盖,或执行InvalidateRect,这部份都被运行

	invoke BeginPaint,hWnd,ADDR Ps
	mov hDC, eax	;获取当前窗口显示句柄

	;将暂存图一次存入当前窗口显示句柄
	invoke BitBlt,hDC,0,0 ,boardW,boardH,memDC3,0,0,SRCCOPY	; redraw current hDC
	invoke EndPaint,hWnd,ADDR Ps  ;结束刷新
	return 0			;返回

	.elseif uMsg == WM_CLOSE

	.elseif uMsg==WM_KEYDOWN 	;若有按键则运行以下代码

	call Get_random		;取乱数以决定柱子和金币位置

	invoke GetAsyncKeyState, VK_LEFT   ;是否按左键
	test eax, 08000h
	jz isRight			;不是左,再查
	;// Process the LEFT ARROW key. 
	cmp action_mode,0		;是否已有动作模式,若有,按键失效
	jnz NoKey 			
	cmp stillness_flag,0		;是否已设定静止,若有,按键失效
	jnz NoKey 			;离开
	mov action_mode,7 		;设定动作模式7
All_here_leave:
	mov action_count,0		;重设模式飞行次数
	invoke InvalidateRect,hWnd,NULL,FALSE   ;启动刷新,FALSE表示不重绘窗口底色             
	jmp NoKey		;离开

isRight:
	invoke GetAsyncKeyState, VK_RIGHT   ;是否右
	test eax, 08000h
	jz isUp			;不是右,再查
	cmp action_mode,0		;是否已有动作模式,若有,按键失效
	jnz NoKey 			;离开
	cmp stillness_flag,0		;是否已设定静止,若有,按键失效
	jnz NoKey 			;if stillness then leave
	mov action_mode,2		;设定动作模式7
	jmp All_here_leave		;离开

isUp:
	invoke GetAsyncKeyState, VK_UP  ;是否上
	test eax, 08000h
	jz isDown			;不是上,再查
	cmp action_mode,0		;是否已有动作模式,若有,按键失效
	jnz NoKey			;离开 
	cmp stillness_flag,0		;是否已设定静止,若有,按键失效
	jnz NoKey 			;if stillness then leave
	mov action_mode,8		;设定动作模式8
	jmp All_here_leave		;离开

isDown:
	invoke GetAsyncKeyState, VK_DOWN  ;是否下
	test eax, 08000h
	jz isSpace			;不是下,再查
	cmp action_mode,0		;是否已有动作模式,若有,按键失效
	jnz NoKey 			;离开
	cmp stillness_flag,0		;是否已设定静止,若有,按键失效
	jnz NoKey 			;if stillness then leave
	mov action_mode,6		;设定动作模式6
	jmp All_here_leave		;离开

isSpace:
	invoke GetAsyncKeyState, VK_SPACE  ;是否空白
	test eax, 08000h
	jz NoKey			;不是空白,离开
	cmp action_mode,0		;是否已有动作模式,若有,按键失效
	jnz NoKey 			;离开
	mov stillness_flag,0		;是否已设定静止,若有,按键失效
	mov action_mode,1		;设定动作模式1
	jmp All_here_leave		;离开

NoKey:

	.elseif uMsg == WM_DESTROY	;离开程式

	invoke DeleteObject,memBmp2	;以下删除/关闭相关句柄
	invoke DeleteObject,memBmp3
	invoke DeleteDC,memDC2
	invoke DeleteDC,memDC3
	invoke DeleteDC,memDC9
	invoke PostQuitMessage,NULL
	return 0 
    .endif

    invoke DefWindowProc,hWnd,uMsg,wParam,lParam	;win32 API 自动扫尾,处理其余讯息
    ret

WndProc endp
;----------------------------------------------------------------------------------------------
;以下程序,检查上移动是否合法,若超过则维持不变
Check_UP proc
	push eax
	push ebx
	mov eax,BirdY
	mov ebx,MovGap
	cmp eax,ebx
	jae @f
	xor ebx,ebx
	xor eax,eax
@@:	sub eax,ebx
	mov BirdY,eax
	pop ebx
	pop eax
	ret
Check_UP endp
;----------------------------------------------------------------------------------------------
;以下程序,检查下移动是否合法,若超过则维持不变
Check_Down proc

	cmp stillness_flag,0	;是否静止?
	jz @f
	ret		;静止模式开启,离开

@@:	push eax
	push ebx
	push ecx
	mov eax,BirdY
	mov ebx,MovGap		;偏移量
	cmp eax,FlyLimitY - MovGap	;是否超过下限,即碰地
	jbe @f			;不是
	mov eax,FlyLimitY		;设定下限
	xor ebx,ebx		;0
	mov stillness_flag,1		;设定静止模式,当完成滚动后,一切静止,只有空白键能解除
	mov ecx,4			;设定动作模式,滚向左
	cmp BirdX,FlyLimitX - 100	;若小鸟接近窗口右边界,则滚左,否则滚右
	jae Right_roll		;
	mov ecx,3			;设定动作模式,滚向右
Right_roll:
	mov action_mode,ecx		;滚左或滚右
	mov action_count,0		;重设动作计数
	
@@:	add eax,ebx		;加移动量
	mov BirdY,eax		;存
	pop ecx
	pop ebx
	pop eax
	ret
Check_Down endp

;-------------------------------------------------
;以下取柱子乱数,每运行这程序一次取一值,分别是(柱子位置),(上或下或无),(长度)
;一个屏幕最多4柱子,所以这程序会运行12次,之后等一个窗口卷动完,会重新再取.
;为什么不一次取完?
;因为以时间作乱数,一次取完会令乱数变得有序...

Get_random proc
	pushad 
	mov esi,Start_Random ;取乱数计数		
	cmp esi,0		;是否0
	jz Get_randomx	;若是,则已取完12个乱数,离开
	dec esi		;调整
	shl esi,3		;x 8
	add esi,offset random_table	;指向乱数限制表
	mov ecx,[esi]	;low limit   取下限
	mov edx,[esi+4]	;hight limit 取上限
	call random	;取乱数,由EAX传回1-256
	cmp ecx,0		;下限?
	jnz @f
	cmp edx,2		;上限?
	jnz @f
	mov ebx,9		;到此处,则取柱子是否上或下
	xor edx,edx	;0
	div ebx		;eax 除9
	mov eax,edx	;edx = 0 - 8
	mov edx,0		;0
	cmp eax,3		
	jbe check_Rand	;0-3 去 check_Rand, edx= 0
	inc edx		;1
	cmp eax,7		
	jbe check_Rand	;4-7  去 check_Rand, edx= 1
	inc edx		;2
	jmp check_Rand 	;8  去 check_Rand, edx= 2  
	;取乱数0-8的用意是,一个窗口可以有0-4个柱子,但上下柱子和无柱子有不同的比重
	;0-3是上柱子,4-7是下柱子,8是无柱子,无柱子的机会较低.
@@:
	add eax,ecx	;不是选上柱子下柱子到此,乱数eax+下限ecx
	cmp eax,edx	;比较上限
	jae Get_randomx	;大于上限则此次取乱数程序作废,等待下次	
	mov edx,eax	;存乱数

check_Rand:		;以下把乱数放入柱子资料区
	mov esi,Start_Random	  ;取乱数计数		
	dec esi		;调整
	shl esi,2		;x4
	add esi,offset pillar1	;柱子资料区,起始位置

	cmp edx,1 		 ;乱数是否1
	ja check_Rand1	;大于
	cmp edx,last_pillar	;是否上次柱子位置,上或下
	mov last_pillar,edx	;保存这次柱子位置,上或下
	jnz check_Rand1	;不是
	inc same_pillar	;增加相同次数
	cmp same_pillar,3	;是否3次
	jb check_Rand1	;不是
	mov same_pillar,0	;重设为0
	xor edx,1		;不容许相同次数超过3次,xor 1,反相
			;这段代码用意是不容许连续三个上柱子或下柱子

check_Rand1:	
	mov [esi],edx	; 存之
	inc Start_Random	;增加柱子乱数计数
	cmp Start_Random,Start_Random_limit	;是否到了限制
	jb Get_randomx	;未到
	mov Start_Random,0 ;no more random ;够了,已取12个柱子乱数,关闭乱数开关
Get_randomx:
	popad
	ret		

Get_random endp
;-------------------------------------------------
;以下乱数子程序,eax 传回1-256
random proc	;ret 0-256
	push ebx
	push edx
	mov eax, 214013h
	imul seed
	sub edx, edx
	add eax, 2531011h
	mov seed, eax		;simple pseudo-random routines have a clear periodicity,
	xchg eax, ebx		;so to add some more entropy and a different serie 
	invoke GetTickCount 
	;callW GetTickCount		;each time (slow but here we don't care), I:
	xor eax, ebx			; - xor with milliseconds
	and eax, 11111111b		; - just use the low bits (so 0<eax<255)
	inc eax			; ajust 1-256
	pop edx
	pop ebx
	ret			;and don't tell me it's not a good way, this is a
random endp
;----------------------------------------------------------------------------------------------
;以下根据柱子资料区,计算并传回柱子在资料图的位置,和要贴到什么地方
Check_pillar proc
	mov ebx,[esi]	;x - axis
	add ebx,boardW	;second hDC
	mov eax,[esi+4]	;up and down
	mov edx,[esi+8]	;get length
	mov ecx,pillar2_offset
	mov edi,boardH - 60
	sub edi,edx
	cmp eax,1
	mov eax,0
	je check_pillarx 	;1
	jb @f		;0
	; 2
	stc
	ret

@@:	; zero
	mov ecx,pillar1_offset
	mov eax,pillarH	;get pillar length
	sub eax,edx
	xor edi,edi		;target y - axis

check_pillarx:

	; ebx - target x-axis
	; edi - target y-axis
	; ecx -  x-axis
	; eax - y-axis
	; edx - length
	clc
	ret

Check_pillar endp
;---------------------------------------------------
end start